package org.tgit.gateway.service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.Cache;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.ResourceAccessException;
import org.tgit.gateway.common.ApiConstants;
import org.tgit.gateway.common.CacheUtils;
import org.tgit.gateway.common.CommonUtil;
import org.tgit.gateway.common.GatewayException;
import org.tgit.gateway.common.MD5Utils;
import org.tgit.gateway.common.MessageQueueUtil;
import org.tgit.gateway.domain.APIServerInfo;
import org.tgit.gateway.domain.ApiGateWayInfo;
import org.tgit.gateway.domain.ApiMonitorInfo;
import org.tgit.gateway.domain.AuthAppApiInfo;
import org.tgit.gateway.domain.ReqSwitchApiService;
import org.tgit.gateway.domain.ServiceAuthorize;
import org.tgit.gateway.domain.User;
import org.tgit.gateway.mapper.GatewayMapper;
import org.tgit.gateway.route.RouteConfig;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Service
public class GatewayService {

	private Logger LOGGER = LoggerFactory.getLogger(GatewayService.class);
	@Autowired
	private GatewayMapper gatewayMapper;
	
	@Autowired
	private RouteConfig routeConfig;

	@Autowired
	private CacheUtils cacheUtils;

	@Resource(name = "gatewayCache")
	private Cache gatewayCache;

	// 一次查询数据库返回的最大数量
	@Value("${system.maxPageSize}")
	private int maxPageSize;

	// 每次任务从网关缓存中获取的最大数量
	@Value("${system.maxGetGateWaySize}")
	private int maxGetGateWaySize;

	// 当前系统对外的端口号
	@Value("${server.port}")
	private int httpPort;
	// 当前系统对外的端口号
	@Value("${service.id}")
	private String serviceId;

	// 并发类容器实例化
	private final Map<String, User> data = new ConcurrentHashMap<>();


	/*********************************
	 * 提供访问接口
	 ***************************************/
	// 初始化数据到缓存,测试用
	public void initCache() {

	}

	/**
	 * 
	 * @Title: reloadAllAppApiInfo
	 * @Description: TODO(加载所有的API-APP信息-全量执行)
	 * @author fanzaijun
	 * @date 2018年10月25日 下午5:55:28 void
	 * @throws (没有异常抛出)
	 */
	public void reloadAllAppApiInfo() {
		// 加载app下所有API信息
		PageHelper.startPage(1, maxPageSize);
		Page<AuthAppApiInfo> authInfos = gatewayMapper.getAuthInfos(null);
		if (authInfos.getTotal() == 0l) {
			LOGGER.info("本次需要授权的APP下API还未全部审批，不注册到网关.");
		}
		// 分页全量
		loadAuthAppApiToGateWay(authInfos.getResult());
		// 获取总页数，第二次查询数据库，从第二页开始，查询同样的条数
		int totalPage = authInfos.getPages();
		for (int i = 2; i <= totalPage; i++) {
			PageHelper.startPage(i, maxPageSize);
			authInfos = gatewayMapper.getAuthInfos(null);
			// 分页全量
			loadAuthAppApiToGateWay(authInfos);
		}
	}

	/**
	 * 刷新所有app-api信息
	 * 
	 * @Title: reloadAllApiInfo
	 * @Description: 加载所有APP下的API信息到网关中
	 * @author fanzaijun
	 * @date 2018年10月25日 下午2:01:08 void
	 * @throws (没有异常抛出)
	 */
	public void reloadAllApiInfo() {
		MessageQueueUtil.removeAllApiServer();
		// 分页查询已经注册并关联服务去的API列表
		PageHelper.startPage(1, maxPageSize);
		Page<APIServerInfo> apiServerInfos = gatewayMapper.queryPageApiService();
		//API信息判断
		if (CommonUtil.isEmpty(apiServerInfos)) {
			LOGGER.info("本次任务没有查询到API的接入信息，则不需要通知网关更新数据");
			return;
		}
		// 将API信息重新注册到网关
		loadApiDataToGateWay(apiServerInfos);
		int totalPage = apiServerInfos.getPages();
		for (int i = 2; i <= totalPage; i++) {
			PageHelper.startPage(i, maxPageSize);
			apiServerInfos = gatewayMapper.queryPageApiService();
			loadApiDataToGateWay(apiServerInfos);
		}
	}

	/**
	 * 
	* @Title: registerApi   
	* @Description: TODO(这里用一句话描述这个方法的作用)
	* @author fanzaijun  
	* @date 2018年10月26日 下午4:37:41   
	* @param apiIds  
	* void  
	* @throws (没有异常抛出)
	 */
	public void registerApi(String[] apiIds) {
		Map<String, Object> param = new HashMap<String, Object>();
		param.put("apiIds", apiIds);
		List<APIServerInfo> apiServerInfos = gatewayMapper.queryApiService(param);
		//获取对应的网关信息
		Map<String, ApiGateWayInfo> apiGateWayInfos = getApiServers(apiServerInfos, true);
		//判断是否存在
		if (CommonUtil.isEmpty(apiGateWayInfos)) {
			//不存在这些api，则进行失败登记
			MessageQueueUtil.addRegisterApiFail(apiIds);
			throw new GatewayException(ApiConstants.BUSSERROR, "传入的API还未审核通过，或没有关联服务器，因此不能注册到网关.");
		}
		// 将API信息注册到网关
		registerApiToGateWay(apiGateWayInfos);
	}

	/**
	 * 
	* @Title: switchApiService   
	* @Description: 网关api服务开与关
	* @author fanzaijun  
	* @date 2018年10月26日 下午4:44:59   
	* @param req  
	* void  
	* @throws (没有异常抛出)
	 */
	public void switchApiService(ReqSwitchApiService req) {
		Map<String, Object> param = new HashMap<String, Object>();
		param.put("apiIds", req.getApiIds());
		List<APIServerInfo> apiServerInfos = gatewayMapper.queryApiService(param);
		//获取对应的网关信息
		Map<String, ApiGateWayInfo> apiGateWayInfos = getApiServers(apiServerInfos, true);
		// 如果是开启服务操作
		if (req.isOnOff()) {
			// 开启操作后，需要重新注册到缓存中
			registerApiToGateWay(apiGateWayInfos);
		} else {
			// 关闭操作的时候需要将网关中API的状态修改为已经关闭
			closeGateWayApi(apiGateWayInfos);
		}
	}

	// 服务授权
	// 将APP下的Token信息注册到网关中
	public void serviceAuthorize(ServiceAuthorize[] apiAuth) {
		// 从数据库中查询到指定的APP下的API的详细信息
		List<AuthAppApiInfo> authInfos = gatewayMapper.getAuthInfos(apiAuth);
		if (CommonUtil.isEmpty(authInfos)) {
			LOGGER.info("本次需要授权的APP下API还未审批tf_f_app_api_school，不注册到网关.");
			throw new GatewayException(ApiConstants.BUSSERROR, "本次需要授权的APP下API还未审批app_api_domain，不注册到网关.");
		}
		loadAuthAppApiToGateWay(authInfos);
	}

	/**
	 ********************************* 定时任务执行方法***************************************
	 */
	/**
	 * 
	* @Title: reloadApiGateWayData   
	* @Description: 将当前数据库中的数据加载到网关中
	* 				1、查询当前所有的API信息，将已经删除或失效的从网关中删除，正常的则重新刷新缓存
	*               2、查询授权信息，并且将有效授权信息更新到缓存中
	* @author fanzaijun  
	* @date 2018年10月26日 下午4:51:23     
	* void  
	* @throws (没有异常抛出)
	 */
	public void reloadApiGateWayData() {
		reloadAllApiInfo();
		reloadAllAppApiInfo();
	}

	//注册失败的API,进行重试
	public void retryRegisterApiToGateWay() {
		//获取注册失败的API
		Map<String, Integer> apis = MessageQueueUtil.getAllRegisterApiFail();
		if (CommonUtil.isEmpty(apis)) {
			return;
		}
		for (Map.Entry<String, Integer> entry : apis.entrySet()) {
			//apiId
			String apiId = entry.getKey();
			//默认是0
			Integer times = entry.getValue();
			//参数携带
			Map<String, Object> param = new HashMap<String, Object>();
			//apiId集合
			param.put("apiIds", new String[] { apiId });
			//数据库拉取
			List<APIServerInfo> apiServerInfos = gatewayMapper.queryApiService(param);
			//获取网关中的信息
			Map<String, ApiGateWayInfo> apiInfos = getApiServers(apiServerInfos, true);
			//apiInfos判断
			if (CommonUtil.isEmpty(apiInfos)) {
				//0+1
				times = times + 1;
				if (times >= ApiConstants.MAX_TRY_TIMES) {
					MessageQueueUtil.removeRegisterApiFail(apiId);
				} else {
					//次数加1
					MessageQueueUtil.modifyRegisterApiFail(apiId, times);
				}
			} else {
				// 将API信息注册到网关
				registerApiToGateWay(apiInfos);
				// 清除注册失败的API
				MessageQueueUtil.removeRegisterApiFail(apiId);
			}
		}
	}
	
	/**
	 * 
	* @Title: saveApiGateWayData   
	* @Description: 网关监控数据保存
	* @author fanzaijun  
	* @date 2018年10月26日 下午5:01:51     
	* void  
	* @throws (没有异常抛出)
	 */
	public void saveApiGateWayData() {
		List<ApiMonitorInfo> apiMonitorInfos = new ArrayList<ApiMonitorInfo>();
		//获取日志
		Queue<String> apiLogQuene = MessageQueueUtil.getApiLogQuene();
		// 获得对象为null或者size为0
		if(apiLogQuene.isEmpty()){
			LOGGER.info("tgit_apigateway---网关日志记录为空");
			return ;
		}
		//每次任务批量入库的条数:maxGetGateWaySize
		for (int i = 0; i < maxGetGateWaySize; i++) {
			String json = apiLogQuene.poll();
			if (CommonUtil.isEmpty(json)) {
				break;
			}
			ApiMonitorInfo apiMonitor = CommonUtil.parseToApiMonitorDetailBean(gatewayCache,json,serviceId);
			if (apiMonitor == null) {
				continue;
			}
			apiMonitorInfos.add(apiMonitor);
			//判断apiMonitorInfos嵌入的条数，100条，向数据库插入一次，插入成功，清除当前的apiMonitorInfos的对象，在最后一次可能不足100行，for循环外面需要加一些逻辑
			if (i != 0 && i % ApiConstants.PRE_COMMIT == 0) {
				LOGGER.info("入库完第" + i + "条API调用数据入库");
				gatewayMapper.batchInsertApiMonitorDetails(apiMonitorInfos);
				apiMonitorInfos.clear();
			}
		}
		//在最后一次可能不足100行，处理情况
		if (!CommonUtil.isEmpty(apiMonitorInfos)) {
			LOGGER.info("入库最后" + apiMonitorInfos.size() + "条API调用数据入库");
			gatewayMapper.batchInsertApiMonitorDetails(apiMonitorInfos);
		}
	}

	/**********************************************
	 * 其他非继承
	 **********************************/
	// 将API信息注册到网关
	private void loadApiDataToGateWay(List<APIServerInfo> apiServerinfos) {
		//是否需要判断接口连通性,传true
		//返回所有的访问URI对应API信息
		Map<String, ApiGateWayInfo> apiInfos = getApiServers(apiServerinfos, true);
		//注册到网关
		registerApiToGateWay(apiInfos);
	}
	// 根据APIID查询对应的部署服务器
	// checkCon = true，在关联时判断当前系统中轮训的API服务器是否处于连通状态,如果为true，则需要将这些去掉
	private Map<String, ApiGateWayInfo> getApiServers(List<APIServerInfo> apiServerinfos, boolean checkCon) {
		if (CommonUtil.isEmpty(apiServerinfos)) {
			LOGGER.info("本次传入的API列表没有查询到API相关信息，可能没有关联服务器、未审核，不需要注册到网关");
			return new HashMap<String, ApiGateWayInfo>();
		}
		Map<String, ApiGateWayInfo> apiInfos = new HashMap<String, ApiGateWayInfo>();
		for (APIServerInfo apiServerInfo : apiServerinfos) {
			String key=apiServerInfo.getAccessUri()+ApiConstants.SYMBOL+apiServerInfo.getApiId();
			//判断是否已经存在
			ApiGateWayInfo apiGateWayInfo = apiInfos.get(key);
			if (apiGateWayInfo == null) {
				// 将从数据库中查询到的API信息转换为网关需要的对象
				apiGateWayInfo = CommonUtil.convertApiGateWayBean(apiServerInfo);
				// 如果当前的状态不需要部署到服务器的话，则不需要设置
				if (apiGateWayInfo == null) {
					continue;
				}
			}
			//获取api的状态
			int apiStatus = apiGateWayInfo.getStatus();
			if (ApiConstants.GATEWAY_API.STATUS_NORMAL == apiStatus) {
				// 需要检查接口的连通性,定时任务去判断（熔断去判断）
				if (checkCon) {
					// 如果当前服务器是处于连通状态的,则加载到网关中
					// 包含在内，说明异常
					if (!MessageQueueUtil.existApiServer(apiServerInfo.getServerId())) {
						//不包含，加入部署URL地址
						//set集合，
						apiGateWayInfo.addDeployServer(CommonUtil.getDeployURL(apiServerInfo));
						apiGateWayInfo.addDeployHost(apiServerInfo.getHost());
					}
				} else {
					//set集合
					apiGateWayInfo.addDeployHost(apiServerInfo.getHost());
					apiGateWayInfo.addDeployServer(CommonUtil.getDeployURL(apiServerInfo));
				}
			}
			//如果当前网关中没有任何服务器，则将API网关基本信息的状态修改为没有服务器
			if (CommonUtil.isEmpty(apiGateWayInfo.getDeployServers())) {
				//状态为3:所有关联的服务器都停止
				apiGateWayInfo.setStatus(ApiConstants.GATEWAY_API.STATUS_NO_SERVER);
			}
			//循环嵌入网关的键值对信息:访问accessURI_apiId:apiGateWayInfo
			apiInfos.put(key, apiGateWayInfo);
		}
		return apiInfos;
	}
	// 将API信息注册到网关中
	private void registerApiToGateWay(Map<String, ApiGateWayInfo> apiGateWayInfos) {
		if (CommonUtil.isEmpty(apiGateWayInfos)) {
			return;
		}
		// 遍历查询到的 API
		for (Map.Entry<String, ApiGateWayInfo> entry : apiGateWayInfos.entrySet()) {
			// redis的缓存的key值的长度不能过长，否则会导致lua无法读取
			// 此处对Redis的key增加一个MD5加密
			// apiInfoKey:TGIT_MD5(ACCESS_URI)
			String apiInfoKey = ApiConstants.GATEWAY_PRE_KEY + MD5Utils.md5(entry.getKey());
			
			ApiGateWayInfo apiGateWayInfo = entry.getValue();

			// API基本信息入缓存库：TGIT_MD5(ACCESS_URI):apiInfo
			Map<String, String> apiInfo = new HashMap<String, String>();
			apiInfo.put("apiId", apiGateWayInfo.getApiId());
			apiInfo.put("status", String.valueOf(apiGateWayInfo.getStatus()));
			apiInfo.put("maxConcurrency", String.valueOf(apiGateWayInfo.getMaxConcurrency()));
			apiInfo.put("deployUri", apiGateWayInfo.getDeployUri());
			cacheUtils.putCacheKeyValue(gatewayCache, apiInfoKey, apiInfo);
			// API部署信息入库
			Set<String> deploySet = apiGateWayInfo.getDeployServers();
			if (!CommonUtil.isEmpty(deploySet)) {
				//部署KEY
				String delpoyInfoKey = ApiConstants.GATEWAY_PRE_KEY + MD5Utils.md5(entry.getKey()) + ApiConstants.GATEWAY_DEPLOY_KEY;
				// 先将该API的关联服务器从缓存中删除后，在增加
				gatewayCache.evict(delpoyInfoKey);
				//加入到缓存中
				Queue<String> quene = new ConcurrentLinkedQueue<String>();
				StringBuilder host = new StringBuilder();
				//TODO需要修改
				for (String deploy : deploySet) {
					if (deploy.startsWith("https:")) {
						host.append(ApiConstants.SEPARATOR).append(deploy.substring(8, deploy.indexOf("/", 8)));
					} else {
						host.append(ApiConstants.SEPARATOR).append(deploy.substring(7, deploy.indexOf("/", 8)));
					}
					routeConfig.addRouteHostUri(apiGateWayInfo);
					quene.add(deploy);
				}
				//TODO:netty网关待写
				//zuul网关时候设置方式:
//				ConfigurationManager.getConfigInstance().setProperty(delpoyInfoKey + ".ribbon.listOfServers",
//						host.toString().substring(1));
				//入缓存库方式:delpoyInfoKey:
				cacheUtils.putCacheKeyValue(gatewayCache, delpoyInfoKey, quene);
				//打印日志
				LOGGER.info(gatewayCache.get(delpoyInfoKey).get() + ":"+ gatewayCache.get(delpoyInfoKey));
			}
			LOGGER.info(gatewayCache.get(apiInfoKey).get() + ":" + gatewayCache.get(apiInfoKey));
		}
	}

	// 加载APP下API的鉴权信息
	private void loadAuthAppApiToGateWay(List<AuthAppApiInfo> authInfos) {
		if (CollectionUtils.isEmpty(authInfos)) {
			return;
		}
		// 遍历APP-API
		for (AuthAppApiInfo authAppApiInfo : authInfos) {
			// cacheKey:accessUri_appcode_accessToken
			String cacheKey = CommonUtil.getAccessUriAppCodeAccessToken(authAppApiInfo);
			// 存入到缓存:
			// 格式是k-v:cacheKey:MD5(authAppApiInfo.getAccessUri())
			cacheUtils.putCacheKeyValue(gatewayCache, cacheKey, MD5Utils.md5(authAppApiInfo.getAccessUri()));
			LOGGER.info(cacheKey+ ":" + MD5Utils.md5(authAppApiInfo.getAccessUri()));
			// cache和并发map保存信息
			MessageQueueUtil.saveAppApiTokenInfo(gatewayCache, authAppApiInfo, serviceId);
		}
	}
	// 关闭API
	private void closeGateWayApi(Map<String, ApiGateWayInfo> apiGateWayInfos) {
		if (CommonUtil.isEmpty(apiGateWayInfos)) {
			return;
		}
		// 遍历查询到的 API
		for (Map.Entry<String, ApiGateWayInfo> entry : apiGateWayInfos.entrySet()) {
			String apiInfoKey = ApiConstants.GATEWAY_PRE_KEY + MD5Utils.md5(entry.getKey());
			cacheUtils.updateMapElement(gatewayCache, apiInfoKey, "status", String.valueOf(ApiConstants.GATEWAY_API.STATUS_CLOSE));
		}

	}
	/***************************************************测试*******************************/

	Flux<User> list() {
		return Flux.fromIterable(this.data.values());
	}

	Flux<User> getById(final Flux<String> ids) {
		return ids.flatMap(id -> Mono.justOrEmpty(this.data.get(id)));
	}

	Mono<User> getById(final String id) {
		return Mono.justOrEmpty(this.data.get(id)).switchIfEmpty(Mono.error(new ResourceAccessException(id)));
	}

	Mono<User> createOrUpdate(final User user) {
		this.data.put(user.getId() + "", user);
		return Mono.just(user);
	}

	Mono<User> delete(final String id) {
		return Mono.justOrEmpty(this.data.remove(id));
	}
}
